昨天,我們讓 Jest 用同一套測試驗收 samples/ 和 fixed/,確認 Gemini 產生的補丁到底有沒有修好 Bug。
不過目前要操作 DevPulse,還是得先開啟 Genkit 的開發工具,再找到 codeReviewFlow、輸入檔案路徑,最後查看分析結果。功能已經完成,使用流程卻還不夠順手。
今天不重寫 AI 邏輯,而是替現有的 Flow 加上一個命令列入口。完成後,只要在專案目錄輸入:
devpulse run samples/sql.cjs
就能讓 DevPulse 分析指定檔案,並在終端機以不同顏色呈現問題嚴重程度。
今天要完成三件事:
run 指令與檔案路徑。codeReviewFlow,不要複製一份 Gemini 分析邏輯。回顧 Day 23,我們已經把程式碼審查封裝成 src/codeReviewFlow.mjs。它接收 { filePath },讀取檔案後呼叫 Gemini,最後回傳固定格式的資料:
summary:整體審查摘要。issues:問題清單,包含 line、severity、category、title、explanation 與 suggestion。因此,CLI 不需要再處理 Prompt、Zod Schema 或 Gemini API。它只要把使用者輸入的路徑交給 codeReviewFlow,再將回傳結果顯示出來即可。
使用者輸入指令
↓
CLI 解析 run 與檔案路徑
↓
codeReviewFlow
↓
Gemini 回傳結構化審查結果
↓
CLI 將結果顯示在終端機
這樣分工有個好處:未來想調整 AI 的分析規則,就修改 Flow;想換輸出格式或增加其他指令,則修改 CLI。兩者不必綁在一起。
在 devpulse/ 專案根目錄執行:
npm install chalk@5 dotenv
chalk 用來替終端機文字加上顏色;dotenv 則負責載入專案根目錄的 .env,讓 CLI 啟動時也能讀到 Gemini API Key。
這裡使用 chalk@5,因為專案入口採用 ES Module 的 import 語法。Chalk 5 本身也是 ES Module,能直接搭配使用。
接著建立 bin/devpulse.mjs:
#!/usr/bin/env node
import "dotenv/config";
import path from "node:path";
import chalk from "chalk";
import { codeReviewFlow } from "../src/codeReviewFlow.mjs";
function showHelp() {
console.log(`
DevPulse - 本機程式碼診斷小幫手
使用方式:
devpulse run <filePath> 分析指定的程式碼
devpulse --help 顯示使用說明
範例:
devpulse run samples/sql.cjs
`);
}
async function main() {
const [command, targetPath] = process.argv.slice(2);
if (!command || command === "--help" || command === "help") {
showHelp();
return;
}
if (command !== "run") {
console.error(chalk.red(`不支援的指令:${command}`));
showHelp();
process.exitCode = 1;
return;
}
if (!targetPath) {
console.error(chalk.red("請提供要分析的檔案路徑。"));
showHelp();
process.exitCode = 1;
return;
}
// 將相對路徑轉成絕對路徑,交給既有的 Flow 讀檔。
const filePath = path.resolve(process.cwd(), targetPath);
console.log(chalk.cyan.bold("\nDevPulse 程式碼審查"));
console.log(chalk.gray(`分析檔案:${filePath}`));
try {
const result = await codeReviewFlow({ filePath });
console.log(`\n${chalk.bold("審查摘要")}`);
console.log(result.summary);
if (!result.issues || result.issues.length === 0) {
console.log(chalk.green("\n✓ 沒有發現問題。"));
return;
}
console.log(chalk.bold(`\n發現 ${result.issues.length} 個問題:`));
for (const issue of result.issues) {
const severityLabel = {
high: chalk.red("HIGH"),
medium: chalk.yellow("MEDIUM"),
low: chalk.cyan("LOW"),
}[issue.severity] ?? chalk.gray(String(issue.severity).toUpperCase());
console.log(
`\n${severityLabel} ${chalk.gray(`Line ${issue.line}`)} [${issue.category}] ${issue.title}`,
);
console.log(` 原因:${issue.explanation}`);
console.log(` 建議:${issue.suggestion}`);
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(chalk.red.bold("\n分析失敗:"), message);
process.exitCode = 1;
}
}
main().catch((error) => {
console.error(chalk.red.bold("CLI 發生未預期錯誤:"), error);
process.exitCode = 1;
});
這份 CLI 刻意只呼叫 codeReviewFlow,不會直接修改原始檔,也不會擅自把分析結果當成修復成功。補丁套用與 Jest 驗收仍維持前幾天建立的流程,避免一個指令在使用者不知情的情況下改動程式碼。
devpulse 指令在 package.json 現有設定中加入 bin 欄位即可,不要整份覆蓋原本的檔案:
{
"bin": {
"devpulse": "./bin/devpulse.mjs"
}
}
如果原本已經有 scripts、dependencies 等欄位,請保留它們,只把 bin 合併進去。接著在專案根目錄執行:
npm link
npm link 會把目前的本機套件連結成可在終端機呼叫的指令。完成後,可以先確認說明頁是否正常:
devpulse --help
再挑一個原始 Bug 範例來分析:
devpulse run samples/sql.cjs
若想先不設定 npm link,也可以直接使用 Node 執行入口檔:
node ./bin/devpulse.mjs run samples/sql.cjs
以 sql.cjs 為例,CLI 的輸出可能會像這樣(實際摘要與措辭會依 Gemini 的回應而變動):
DevPulse 程式碼審查
分析檔案:.../samples/sql.cjs
審查摘要
findUser 直接把輸入值拼進 SQL,可能造成 SQL Injection。
發現 1 個問題:
HIGH Line 3 [security] SQL 字串直接拼接使用者輸入
原因:使用者輸入被直接組合進查詢字串。
建議:改用 ? 佔位符,並把輸入值放進參數陣列。
這裡用紅色標示 high、黃色標示 medium、青色標示 low。顏色只是呈現方式,嚴重程度仍以 ReviewSchema 定義及 Flow 回傳的 severity 為準;不能因為終端機顯示紅色,就跳過人工確認。
如果看到「找不到檔案」,先確認指令中的相對路徑是以目前終端機所在目錄為基準;如果看到 API Key 或模型呼叫相關錯誤,則檢查 .env 是否存在、金鑰名稱是否與 Genkit 設定一致。CLI 的錯誤處理能把失敗訊息顯示出來,但不會自動修好環境設定。
今天最大的收穫不是替終端機加上顏色,而是把已經完成的 AI Flow 包裝成真正順手的工具。CLI 負責接收指令、驗證輸入與呈現結果;codeReviewFlow 負責分析。把責任切清楚,之後才有空間增加自動修補、測試與報告輸出。
也要記住,CLI 印出「分析完成」只代表 Flow 有回傳結果,不代表程式碼沒有 Bug,更不代表修復通過測試。可靠的流程仍然要靠明確的測試條件驗收。
下一篇 Day 27,會讓 Antigravity 接手更多重複操作,嘗試把套用補丁與執行測試串成更自動化的工作流程。
bin/devpulse.mjs,解析 run 指令與檔案路徑codeReviewFlow
npm link 在終端機使用 devpulse run <filePath>